package ch.thomasmueller.saltyfiles.transformer;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

import ch.thomasmueller.saltyfiles.data.AlgoritmesEnum.Algoritmes;
import ch.thomasmueller.saltyfiles.io.InputFileHandler;
import ch.thomasmueller.saltyfiles.io.OutputFileHandler;
import edu.vt.middleware.crypt.symmetric.AES;
import edu.vt.middleware.crypt.symmetric.Blowfish;
import edu.vt.middleware.crypt.symmetric.RC2;
import edu.vt.middleware.crypt.symmetric.RC4;
import edu.vt.middleware.crypt.symmetric.RC6;
import edu.vt.middleware.crypt.symmetric.Rijndael;
import edu.vt.middleware.crypt.symmetric.SymmetricAlgorithm;
import edu.vt.middleware.crypt.symmetric.Twofish;

public class NewTransformer {
	private char[] password = null;
	private byte[] salt = null;
	private static final byte[] zipSignature = new byte[] { 0x50, 0x4b, 0x03,
			0x04 };

	public NewTransformer(char[] aPassoword, byte[] someSalt) {
		password = aPassoword;
		salt = someSalt;
	}

	/**
	 * Transforms the file. encryps and decryps
	 * 
	 * @param aMode
	 *            Encryption or decryption mode
	 * @param source
	 *            sourcefile
	 * @param target
	 *            targetfile
	 * @param algEnum
	 *            algoritme
	 * @throws Exception
	 */
	public void transform(int aMode, InputFileHandler source,
			OutputFileHandler target, Algoritmes algEnum) throws Exception {
		FileInputStream fis = source.getFileInputStream();
		FileOutputStream fos = target.getFileOutputStream();
		SymmetricAlgorithm algorithm = null;

		switch (algEnum) {
		case Twofish:
			algorithm = new Twofish();
			algorithm.setIV(algorithm.getRandomIV());
			break;
		case Rijndael:
			algorithm = new Rijndael();
			algorithm.setIV(algorithm.getRandomIV());
			break;
		case Blowfish:
			algorithm = new Blowfish();
			algorithm.setIV(algorithm.getRandomIV());
			break;
		case RC2:
			algorithm = new RC2();
			algorithm.setIV(algorithm.getRandomIV());
			break;
		case RC4:
			algorithm = new RC4();
			break;
		case RC6:
			algorithm = new RC6();
			algorithm.setIV(algorithm.getRandomIV());
			break;
		default:
			algorithm = new AES();
			algorithm.setIV(algorithm.getRandomIV());
		}

		// Initialize algorithm
		String fixedPassword = fixPassword(new String(password), algEnum);
		System.out.println("DEBUG password:" + fixedPassword);
		algorithm.setKey(new SecretKeySpec(fixedPassword.getBytes(), algorithm
				.toString()));

		// Initialize encryption mode
		if (Cipher.ENCRYPT_MODE == aMode) {
			algorithm.initEncrypt();
		} else {
			algorithm.initDecrypt();
		}

		// ignore file signature of the archive
		fos.write(zipSignature);
		fis.skip(zipSignature.length);

		// Open streams and encrypt/decrypt data
		InputStream in = new BufferedInputStream(fis);
		OutputStream out = new BufferedOutputStream(fos);
		try {
			if (Cipher.ENCRYPT_MODE == aMode) {
				algorithm.encrypt(in, out);
			} else {
				algorithm.decrypt(in, out);
			}
		} catch (Exception e) {
			System.err.println("Encryption exception:");
			System.err.println(e.getMessage());
			Print("Exception", aMode, source, target, algEnum, algorithm,
					new String(password));
			e.printStackTrace();
		} finally {
			in.close();
			out.flush();
			out.close();
		}
	}

	/**
	 * fixes password
	 * 
	 * @param password
	 *            current password
	 * @param algorithm
	 *            algoritme
	 * @param algEnum
	 *            algoritme
	 * @return optimized password
	 */
	public String fixPassword(String password, Algoritmes algEnum) {
		switch (algEnum) {
		case AES: // 128, 192, 256
			return changePassword(password, new int[] { 128, 192, 256 });
		case Twofish: // 128, 256
			return changePassword(password, new int[] { 128, 256 });
		case Rijndael: // 128, 192, 256
			return changePassword(password, new int[] { 128, 192, 256 });
		case Blowfish: // 32-448
			return changePassword(password, 32, 448);
		case RC2: // 8-1024
			return changePassword(password, 8, 1024);
		case RC4: // 40-2048
			return changePassword(password, 40, 2048);
		case RC6: // 128, 192, 259
			return changePassword(password, new int[] { 128, 256 });
		default:
			return changePassword(password, new int[] { 128 });
		}
	}

	/**
	 * Optimizes the password
	 * 
	 * @param password
	 * @param allowedSizes
	 * @return Optimized password
	 */
	public String changePassword(String password, int[] allowedSizes) {
		int passwordBitLength = password.length() * 8;
		String temp = password;
		int counter = 0;
		if (allowedSizes[allowedSizes.length - 1] < passwordBitLength) {
			return password.substring(0,
					(allowedSizes[allowedSizes.length - 1] / 8));
		} else if (allowedSizes[0] > passwordBitLength) {
			while (allowedSizes[0] > temp.length() * 8) {
				temp += password.charAt(counter);
				counter++;
				if (counter == password.length()) {
					counter = 0;
				}
			}
			System.out.println("DEBUG changedPassword(1): " + temp.length());
			return temp;
		} 
		else {
			for (int i = 0; i < allowedSizes.length - 1; i++) {
				if (allowedSizes[i] < passwordBitLength
						&& allowedSizes[i + 1] > passwordBitLength) {
					System.out.println("DEBUG minlength, current, newlength: " + allowedSizes[i] + " - " + passwordBitLength + " - " + allowedSizes[i+1]);
					while (allowedSizes[i + 1] > temp.length() * 8) {
						temp += password.charAt(counter);
						counter++;
						if (counter == password.length()) {
							counter = 0;
						}
					}
					System.out.println("DEBUG changedPassword(2): " + temp.length());
					return temp;
				}
			}
		}
		System.out.println("DEBUG changedPassword(3): " + password.length());
		return password;
	}

	/**
	 * Optimizes the password
	 * 
	 * @param password
	 * @param min
	 * @param max
	 * @return Optimized password
	 */
	private String changePassword(String password, int min, int max) {
		if (password.length() * 8 >= min && password.length() * 8 <= max) {
			return password;
		} else if (password.length() * 8 < min) {
			return changePassword(password + password, min, max);
		} else {
			return password.substring(0, (max / 8));
		}
	}

	/**
	 * Prints multiple variables
	 * 
	 * @param text
	 * @param aMode
	 * @param source
	 * @param target
	 * @param algEnum
	 * @param algorithm
	 * @param password
	 */
	private void Print(String text, int aMode, InputFileHandler source,
			OutputFileHandler target, Algoritmes algEnum,
			SymmetricAlgorithm algorithm, String password) {
		System.out.println("Print Transformer - " + text);
		System.out.println(Cipher.ENCRYPT_MODE == aMode ? "\tencryption mode."
				: "\tdecryption mode.");
		System.out.println("\tSource: " + source.toString());
		System.out.println("\tTarget: " + target.toString());
		System.out.println("\tAlgEnum:" + algEnum.toString());
		System.out.println("\tPassword:" + password);
	}
}
